ostree_sysroot_deployment_unlock
ostree_sysroot_deployment_set_pinned
ostree_sysroot_deployment_can_soft_reboot
-ostree_sysroot_deployment_prepare_next_root
+ostree_sysroot_deployment_set_soft_reboot
+ostree_sysroot_clear_soft_reboot
ostree_sysroot_write_deployments
ostree_sysroot_write_deployments_with_options
ostree_sysroot_write_origin_file
AC_STRUCT_TIMEZONE
AC_CHECK_HEADER([sys/xattr.h],,[AC_MSG_ERROR([You must have sys/xattr.h from glibc])])
+dnl new mount api
+AC_CHECK_FUNCS([open_tree])
+AM_CONDITIONAL([HAVE_SOFT_REBOOT], [test x$ac_cv_func_open_tree = xyes])
+AM_COND_IF([HAVE_SOFT_REBOOT],
+ [AC_DEFINE([HAVE_SOFT_REBOOT], 1, [Define if we have soft reboots])])
+
AS_IF([test "$YACC" != "bison -y"], [AC_MSG_ERROR([bison not found but required])])
AC_SUBST([LIBS_PRIVATE])
<title>Description</title>
<para>
- Prepare the deployment at INDEX for a systemd soft reboot. INDEX must be in range and not reference the currently booted deployment.
- It is recommended to immediately follow this with an involcation of <command>systemctl soft-reboot</command>.
+ Prepare (or unset) the deployment at INDEX for a systemd soft reboot by mounting <literal>/run/nextroot</literal>.
+ INDEX must be in range and not reference the currently booted deployment.
</para>
<para>
- It is not supported to soft reboot into a deployment with a different kernel than the booted one.
+ It is not supported to soft reboot into a deployment with a different kernel state (including initramfs) than the booted one.
</para>
+
+ <para>
+ If a soft reboot is initiated for a deployment that is not staged, while a staged deployment is active, the staged
+ deployment will be automatically cleared.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--reboot</option></term>
+
+ <listitem><para>
+ Initiate a soft reboot.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--reset</option></term>
+
+ <listitem><para>
+ Clear a pending soft reboot state instead of initializing one. When this is provided,
+ a deployment index is not required.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
</refsect1>
<refsect1>
global:
ostree_deployment_is_soft_reboot_target;
ostree_sysroot_deployment_can_soft_reboot;
- ostree_sysroot_deployment_prepare_next_root;
+ ostree_sysroot_deployment_set_soft_reboot;
+ ostree_sysroot_clear_soft_reboot;
} LIBOSTREE_2025.2;
gboolean
_ostree_prepare_soft_reboot (GError **error)
{
+#ifdef HAVE_SOFT_REBOOT
const char *sysroot_path = "/sysroot";
const char *target_deployment = ".";
if (!otcore_mount_etc (config, &metadata_builder, OTCORE_RUN_NEXTROOT, error))
return FALSE;
- // Note we should have inherited the readonly sysroot
- g_autofree char *target_sysroot = g_build_filename (OTCORE_RUN_NEXTROOT, "sysroot", NULL);
- if (mount (sysroot_path, target_sysroot, NULL, MS_BIND | MS_SILENT, NULL) < 0)
- return glnx_throw_errno_prefix (error, "failed to bind mount sysroot");
+ // And set up /sysroot. Here since we hardcode composefs, we also hardcode
+ // having a read-only /sysroot.
+ g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_SYSROOT_RO,
+ g_variant_new_boolean (true));
+ {
+ struct mount_attr attr = { .attr_set = MOUNT_ATTR_RDONLY };
+ glnx_autofd int sysroot_fd
+ = open_tree (AT_FDCWD, sysroot_path, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+ if (sysroot_fd < 0)
+ return glnx_throw_errno_prefix (error, "open_tree(%s)", sysroot_path);
+ if (mount_setattr (sysroot_fd, "", AT_EMPTY_PATH, &attr, sizeof (struct mount_attr)) < 0)
+ return glnx_throw_errno_prefix (error, "syscall(mount_setattr) of sysroot");
+ g_autofree char *target_sysroot = g_build_filename (OTCORE_RUN_NEXTROOT, "sysroot", NULL);
+ if (move_mount (sysroot_fd, "", -1, target_sysroot, MOVE_MOUNT_F_EMPTY_PATH) < 0)
+ return glnx_throw_errno_prefix (error, "syscall(move_mount) of sysroot");
+
+ g_debug ("initialized /sysroot");
+ }
/* This can be used by other things to signal ostree is in use */
{
}
return TRUE;
+#else
+ return glnx_throw (error, "soft reboot not supported");
+#endif
}
*/
#define EARLY_PRUNE_SAFETY_MARGIN_SIZE (1 << 20) /* 1 MB */
+static void impl_clear_soft_reboot (void);
+
/*
* Like symlinkat() but overwrites (atomically) an existing
* symlink.
}
const guint nonstaged_current_len = self->deployments->len - (self->staged_deployment ? 1 : 0);
+ gboolean removed_soft_reboot_target = (self->soft_reboot_target_deployment != NULL);
+ for (guint i = 0; i < new_deployments->len; i++)
+ {
+ OstreeDeployment *deployment = new_deployments->pdata[i];
+ if (ostree_deployment_is_soft_reboot_target (deployment))
+ {
+ removed_soft_reboot_target = FALSE;
+ break;
+ }
+ }
+ if (removed_soft_reboot_target)
+ {
+ g_debug ("Removing soft reboot target");
+ impl_clear_soft_reboot ();
+ }
+
/* Assign a bootserial to each new deployment.
*/
assign_bootserials (new_deployments);
&opts, out_new_deployment, cancellable, error);
}
+/* Ensure ostree-finalize-staged.service is started */
+gboolean
+_ostree_sysroot_ensure_finalize_staged_service (GError **error)
+{
+ // The service which performs finalization
+ const char *svc = "ostree-finalize-staged.service";
+
+ const char *const systemctl_argv[] = { "systemctl", "start", "--quiet", svc, NULL };
+ int estatus;
+ if (!g_spawn_sync (NULL, (char **)systemctl_argv, NULL,
+ G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL,
+ &estatus, error))
+ return FALSE;
+ if (!g_spawn_check_exit_status (estatus, error))
+ return glnx_prefix_error (error, "Failed to start %s", svc);
+
+ return TRUE;
+}
+
/**
* ostree_sysroot_stage_tree_with_options:
* @self: Sysroot
GCancellable *cancellable, GError **error)
{
GLNX_AUTO_PREFIX_ERROR ("Staging deployment", error);
- // The service which performs finalization
- const char *svc = "ostree-finalize-staged.service";
if (!_ostree_sysroot_ensure_writable (self, error))
return FALSE;
if (booted_deployment == NULL)
return glnx_prefix_error (error, "Cannot stage deployment");
- const char *const systemctl_argv[] = { "systemctl", "start", "--quiet", svc, NULL };
- int estatus;
- if (!g_spawn_sync (NULL, (char **)systemctl_argv, NULL,
- G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL,
- &estatus, error))
+ // Staging always resets soft reboot state by default
+ if (!ostree_sysroot_clear_soft_reboot (self, cancellable, error))
+ return FALSE;
+
+ if (!_ostree_sysroot_ensure_finalize_staged_service (error))
return FALSE;
- if (!g_spawn_check_exit_status (estatus, error))
- return glnx_prefix_error (error, "Failed to start %s", svc);
g_autoptr (OstreeDeployment) deployment = NULL;
if (!sysroot_initialize_deployment (self, osname, revision, origin, opts, &deployment,
return TRUE;
}
+struct PrepareRootChildSetupContext
+{
+ const char *deployment_path;
+ int rootns_fd;
+};
+
+static inline void
+prepare_root_child_setup (gpointer data)
+{
+ struct PrepareRootChildSetupContext *ctx = data;
+ // Enter the root namespace first to escape the overlayfs context
+ int rc = setns (ctx->rootns_fd, CLONE_NEWNS);
+ if (rc < 0)
+ err (1, "setns");
+ // Then change to the deployment directory in the root namespace
+ rc = chdir (ctx->deployment_path);
+ if (rc < 0)
+ err (1, "chdir");
+}
+
+static gboolean _ostree_sysroot_finalize_impl_staged_deployment (OstreeSysroot *self,
+ GCancellable *cancellable,
+ GError **error);
+
/* Invoked at shutdown time by ostree-finalize-staged.service */
static gboolean
_ostree_sysroot_finalize_staged_inner (OstreeSysroot *self, GCancellable *cancellable,
GError **error)
{
- /* It's totally fine if there's no staged deployment; perhaps down the line
- * though we could teach the ostree cmdline to tell systemd to activate the
- * service when a staged deployment is created.
- */
- if (!self->staged_deployment)
+ /* Check if we have anythign to do */
+ if (!self->staged_deployment && !self->soft_reboot_target_deployment)
{
- ot_journal_print (LOG_INFO, "No deployment staged for finalization");
+ ot_journal_print (LOG_INFO, "No deployment staged for finalization or soft reboot");
return TRUE;
}
+ if (!_ostree_sysroot_finalize_impl_staged_deployment (self, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+_ostree_sysroot_finalize_impl_staged_deployment (OstreeSysroot *self, GCancellable *cancellable,
+ GError **error)
+{
+ if (!self->staged_deployment)
+ return TRUE;
+
/* Check if finalization is locked. */
gboolean locked = false;
(void)g_variant_lookup (self->staged_deployment_data, _OSTREE_SYSROOT_STAGED_KEY_LOCKED, "b",
return TRUE;
}
-struct PrepareRootChildSetupContext
-{
- const char *deployment_path;
- int rootns_fd;
-};
-
-static inline void
-prepare_root_child_setup (gpointer data)
-{
- struct PrepareRootChildSetupContext *ctx = data;
- // Enter the root namespace first to escape the overlayfs context
- int rc = setns (ctx->rootns_fd, CLONE_NEWNS);
- if (rc < 0)
- err (1, "setns");
- // Then change to the deployment directory in the root namespace
- rc = chdir (ctx->deployment_path);
- if (rc < 0)
- err (1, "chdir");
-}
-
/**
* ostree_sysroot_deployment_can_soft_reboot:
* @self: The #OstreeSysroot object.
return false;
}
+static void
+impl_clear_soft_reboot (void)
+{
+ int flags = G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL;
+ // If we failed to initialize the soft reboot, ensure that we've unwound any mounts
+ const char *umount_argv[] = { "umount", "-R", "/run/nextroot", NULL };
+ // To aid debugging allow skipping cleanup on failure
+ if (!g_getenv ("OSTREE_SKIP_NEXTROOT_CLEANUP"))
+ g_spawn_sync (NULL, (char **)umount_argv, NULL, flags, NULL, NULL, NULL, NULL, NULL, NULL);
+
+ (void)unlinkat (AT_FDCWD, OTCORE_RUN_NEXTROOT_BOOTED, 0);
+}
+
/**
- * ostree_sysroot_deployment_prepare_next_root
+ * ostree_sysroot_deployment_set_soft_reboot:
* @self: Sysroot
* @deployment: Deployment to prepare /run/nextroot
* @allow_kernel_skew: Continue even if there is a kernel mismatch
* Since: TODO
*/
gboolean
-ostree_sysroot_deployment_prepare_next_root (OstreeSysroot *self, OstreeDeployment *deployment,
- gboolean allow_kernel_skew, GCancellable *cancellable,
- GError **error)
+ostree_sysroot_deployment_set_soft_reboot (OstreeSysroot *self, OstreeDeployment *deployment,
+ gboolean allow_kernel_skew, GCancellable *cancellable,
+ GError **error)
{
+#ifdef HAVE_SOFT_REBOOT
GLNX_AUTO_PREFIX_ERROR ("Preparing /run/nextroot for a soft-reboot", error);
if (!ostree_sysroot_deployment_can_soft_reboot (self, deployment) && !allow_kernel_skew)
- {
- return glnx_throw (error, "Cannot soft-reboot to deployment with different kernel");
- }
+ return glnx_throw (error, "Cannot soft-reboot to deployment with different kernel state");
- // For targeting a staged deployment, we finalize now to ensure that we have /etc
- if (ostree_deployment_is_staged (deployment))
+ if (!_ostree_sysroot_ensure_finalize_staged_service (error))
+ return FALSE;
+
+ // Preparing a soft reboot while a staged deployment is active, but targeting
+ // a deployment other than the staged one will unset the staged state.
+ if (self->staged_deployment != NULL && deployment != self->staged_deployment)
{
- if (!_ostree_sysroot_finalize_staged (self, NULL, error))
+ g_autoptr (GPtrArray) current_deployments = ostree_sysroot_get_deployments (self);
+ g_assert (current_deployments->len > 0);
+ g_assert (current_deployments->pdata[0] == self->staged_deployment);
+ g_ptr_array_remove_index (current_deployments, 0);
+ if (!ostree_sysroot_write_deployments (self, current_deployments, cancellable, error))
return FALSE;
}
g_autofree char *deployment_relpath = ostree_sysroot_get_deployment_dirpath (self, deployment);
+ // We only support queuing a soft reboot from a booted host right now, so ignore self->sysroot_fd
+ g_assert (self->booted_deployment);
g_autofree char *deployment_fullpath = g_build_filename ("/sysroot", deployment_relpath, NULL);
gint estatus;
const char *argv[] = { "ostree", "admin", "impl-prepare-soft-reboot", NULL };
+ // The outer CLI entered a mount namespace; escape it
glnx_autofd int rootns_fd = -1;
if (!glnx_openat_rdonly (AT_FDCWD, "/proc/1/ns/mnt", TRUE, &rootns_fd, error))
return FALSE;
if (!g_spawn_check_exit_status (estatus, error))
{
- int flags = G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL;
- // If we failed to initialize the soft reboot, ensure that we've unwound any mounts
- const char *umount_argv[] = { "umount", "-R", "/run/nextroot", NULL };
- // To aid debugging allow skipping cleanup on failure
- if (!g_getenv ("OSTREE_SKIP_NEXTROOT_CLEANUP"))
- g_spawn_sync (NULL, (char **)umount_argv, NULL, flags, NULL, NULL, NULL, NULL, NULL, NULL);
+ impl_clear_soft_reboot ();
return FALSE;
}
- ot_journal_print (LOG_INFO, "Set up soft reboot at /run/nextroot");
+ g_debug ("Soft reboot setup complete");
- return TRUE;
+ // Last step
+ return write_deployments_finish (self, cancellable, error);
+#else
+ return glnx_throw (error, "soft reboot not supported");
+#endif
+}
+
+/**
+ * ostree_sysroot_clear_soft_reboot:
+ * @self: Sysroot
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * If there is a soft reboot queued in /run/nextroot, clear it. If one
+ * is not queued, this function successfully does nothing.
+ *
+ * Since: TODO
+ */
+gboolean
+ostree_sysroot_clear_soft_reboot (OstreeSysroot *self, GCancellable *cancellable, GError **error)
+{
+ if (!self->soft_reboot_target_deployment)
+ return TRUE;
+
+ impl_clear_soft_reboot ();
+
+ g_debug ("Cleared soft reboot queued state");
+
+ return write_deployments_finish (self, cancellable, error);
}
/**
dev_t root_device;
ino_t root_inode;
/* The device inode for a queued soft reboot deployment */
- gboolean have_nextroot;
+ gboolean expecting_nextroot;
dev_t nextroot_device;
ino_t nextroot_inode;
int bootversion;
int subbootversion;
OstreeDeployment *booted_deployment;
+ OstreeDeployment *soft_reboot_target_deployment;
OstreeDeployment *staged_deployment;
GVariant *staged_deployment_data;
// True if loaded_ts is initialized
gboolean _ostree_sysroot_reload_staged (OstreeSysroot *self, GError **error);
+gboolean _ostree_sysroot_ensure_finalize_staged_service (GError **error);
+
gboolean _ostree_sysroot_finalize_staged (OstreeSysroot *self, GCancellable *cancellable,
GError **error);
gboolean _ostree_sysroot_boot_complete (OstreeSysroot *self, GCancellable *cancellable,
ret_deployment->device = stbuf.st_dev;
ret_deployment->inode = stbuf.st_ino;
- g_debug ("Deployment %s.%d unlocked=%d", treecsum, deployserial, ret_deployment->unlocked);
+ g_debug ("Deployment %s.%d unlocked=%d dev=%" G_GUINT64_FORMAT " ino=%" G_GUINT64_FORMAT,
+ treecsum, deployserial, ret_deployment->unlocked, ret_deployment->device,
+ ret_deployment->inode);
if (is_booted_deployment)
self->booted_deployment = g_object_ref (ret_deployment);
{
GLNX_AUTO_PREFIX_ERROR ("Loading nextroot", error);
// Reset state
- self->have_nextroot = FALSE;
+ self->expecting_nextroot = FALSE;
+ g_clear_object (&self->soft_reboot_target_deployment);
glnx_autofd int fd = -1;
if (!ot_openat_ignore_enoent (AT_FDCWD, OTCORE_RUN_NEXTROOT_BOOTED, &fd, error))
return FALSE;
// If there's no such file, we're done
if (fd == -1)
- return TRUE;
+ {
+ g_debug ("No %s", OTCORE_RUN_NEXTROOT_BOOTED);
+ return TRUE;
+ }
// Parse the GVariant metadata from this; search for OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO
// to find similar code.
if (!backing_devino)
return glnx_throw (error, "Missing %s key in %s", OTCORE_RUN_BOOTED_KEY_BACKING_ROOTDEVINO,
OTCORE_RUN_NEXTROOT_BOOTED);
-
// Load the device/inode, and we're done
g_variant_get (backing_devino, "(tt)", &backing_dev, &backing_ino);
- self->have_nextroot = TRUE;
+ g_debug ("Expecting nextroot dev %" G_GUINT64_FORMAT " ino %" G_GUINT64_FORMAT, backing_dev,
+ backing_ino);
+
+ self->expecting_nextroot = TRUE;
self->nextroot_device = (dev_t)backing_dev;
self->nextroot_inode = (ino_t)backing_ino;
g_ptr_array_insert (deployments, 0, g_object_ref (self->staged_deployment));
/* Synchronize internal state now that we've loaded all deployments */
+ g_debug ("expecting nextroot: %d", self->expecting_nextroot);
for (guint i = 0; i < deployments->len; i++)
{
OstreeDeployment *deployment = deployments->pdata[i];
ostree_deployment_set_index (deployment, i);
g_assert (deployment->devino_initialized);
- if (self->have_nextroot && deployment->device == self->nextroot_device
+ if (self->expecting_nextroot && deployment->device == self->nextroot_device
&& deployment->inode == self->nextroot_inode)
{
deployment->soft_reboot_target = TRUE;
+ g_assert (!self->soft_reboot_target_deployment);
+ self->soft_reboot_target_deployment = g_object_ref (deployment);
}
}
+ if (self->expecting_nextroot && !self->soft_reboot_target_deployment)
+ {
+ g_debug ("Soft reboot target not found");
+ if (!glnx_unlinkat (AT_FDCWD, OTCORE_RUN_NEXTROOT_BOOTED, 0, error))
+ return FALSE;
+ self->expecting_nextroot = FALSE;
+ }
+
/* Determine whether we're "physical" or not, the first time we load deployments */
if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED)
{
_OSTREE_PUBLIC gboolean ostree_sysroot_deployment_can_soft_reboot (OstreeSysroot *self,
OstreeDeployment *deployment);
-_OSTREE_PUBLIC gboolean ostree_sysroot_deployment_prepare_next_root (OstreeSysroot *self,
- OstreeDeployment *deployment,
- gboolean allow_kernel_skew,
- GCancellable *cancellable,
- GError **error);
+_OSTREE_PUBLIC gboolean ostree_sysroot_deployment_set_soft_reboot (OstreeSysroot *self,
+ OstreeDeployment *deployment,
+ gboolean allow_kernel_skew,
+ GCancellable *cancellable,
+ GError **error);
+
+_OSTREE_PUBLIC gboolean ostree_sysroot_clear_soft_reboot (OstreeSysroot *self,
+ GCancellable *cancellable,
+ GError **error);
_OSTREE_PUBLIC
gboolean ostree_sysroot_deployment_kexec_load (OstreeSysroot *self, OstreeDeployment *deployment,
// from ostree-prepare-root.
#define OTCORE_RUN_BOOTED "/run/ostree-booted"
// Written by ostree-soft-reboot.c with metadata about /run/nextroot
+// that is then processed by ostree-boot-complete.c and turned into
+// the canonical /run/ostree-booted.
#define OTCORE_RUN_NEXTROOT_BOOTED "/run/ostree/nextroot-booted"
// This key will be present if composefs was successfully used.
#define OTCORE_RUN_BOOTED_KEY_COMPOSEFS "composefs"
#include "ot-admin-functions.h"
#include "otutil.h"
-static GOptionEntry options[] = { { NULL } };
+static gboolean opt_reboot;
+static gboolean opt_reset;
+
+static GOptionEntry options[]
+ = { { "reboot", 0, 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a soft reboot on success",
+ NULL },
+ { "reset", 0, 0, G_OPTION_ARG_NONE, &opt_reset, "Undo queued soft reboot state", NULL },
+ { NULL } };
gboolean
ot_admin_builtin_prepare_soft_reboot (int argc, char **argv, OstreeCommandInvocation *invocation,
cancellable, error))
return FALSE;
+ if (opt_reset)
+ return ostree_sysroot_clear_soft_reboot (sysroot, cancellable, error);
+
if (argc < 2)
{
ot_util_usage_error (context, "INDEX must be specified", error);
return FALSE;
}
- if (!ostree_sysroot_deployment_prepare_next_root (sysroot, target_deployment, FALSE, cancellable,
- error))
+ if (!ostree_sysroot_deployment_set_soft_reboot (sysroot, target_deployment, FALSE, cancellable,
+ error))
return FALSE;
+ if (opt_reboot)
+ {
+ execlp ("systemctl", "systemctl", "soft-reboot", NULL);
+ return glnx_throw_errno_prefix (error, "exec(systemctl soft-reboot)");
+ }
+
return TRUE;
}
"Change the finalization locking state of the staged deployment" },
{ "boot-complete", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
ot_admin_builtin_boot_complete, "Internal command to run at boot after an update was applied" },
+#ifdef HAVE_SOFT_REBOOT
{ "impl-prepare-soft-reboot", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
ot_admin_builtin_impl_prepare_soft_reboot, "Internal command to prepare soft reboot" },
+ { "prepare-soft-reboot", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_prepare_soft_reboot,
+ "Prepare deployment for soft-reboot" },
+#endif
{ "state-overlay", OSTREE_BUILTIN_FLAG_NO_REPO | OSTREE_BUILTIN_FLAG_HIDDEN,
ot_admin_builtin_state_overlay, "Internal command to assemble a state overlay" },
{ "init-fs", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_init_fs,
"rollback strings" },
{ "post-copy", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_post_copy,
"Update the repo and deployments as needed after a copy" },
- { "prepare-soft-reboot", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_prepare_soft_reboot,
- "Prepare deployment for soft-reboot" },
{ "set-origin", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_set_origin,
"Set Origin and create a new origin file" },
{ "status", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_status, "List deployments" },
. ${KOLA_EXT_DATA}/libinsttest.sh
-require_writable_sysroot
prepare_tmpdir
+echo "testing boot=${AUTOPKGTEST_REBOOT_MARK:-}"
+
+# Print this by default on each boot
+ostree admin status
+
+# Verify /sysroot readonly by default on each boot
+test '!' -w /sysroot
+findmnt -J /sysroot > findmnt.json
+assert_jq findmnt.json '.filesystems[0].options | contains("ro")'
+# But mount it writable now so we can make test commits conveniently
+require_writable_sysroot
+
+assert_soft_reboot_count() {
+ assert_streq $(systemctl show -P SoftRebootsCount) $1
+}
+
case "${AUTOPKGTEST_REBOOT_MARK:-}" in
"")
# xref https://github.com/coreos/coreos-assembler/pull/2814
systemctl mask --now zincati
- assert_streq $(systemctl show -P SoftRebootsCount) 0
+ assert_soft_reboot_count 0
assert_status_jq '.deployments[0].pending | not' '.deployments[0].["soft-reboot-target"] | not'
# Create a synthetic commit for upgrade
# Deploy the new commit normally first
ostree admin deploy --stage soft-reboot-test
- assert_status_jq '.deployments[0].pending' '.deployments[0].["soft-reboot-target"] | not'
+ assert_status_jq '.deployments[0].staged' '.deployments[0].["soft-reboot-target"] | not'
# Test prepare-soft-reboot command
echo "Testing prepare-soft-reboot..."
# Test human readable format
ostree admin status > status.txt
- assert_file_has_content_literal status.txt '(pending) (soft-reboot)'
+ assert_file_has_content_literal status.txt '(staged) (soft-reboot)'
# And via JSON
assert_status_jq '.deployments[0].pending' '.deployments[0].["soft-reboot-target"]'
- # Verify the internal state file
test -f /run/ostree/nextroot-booted
+ mountpoint /run/nextroot
/tmp/autopkgtest-soft-reboot "2"
;;
"2")
# After soft reboot, verify we're running the new deployment
echo "Verifying post-soft-reboot state..."
- assert_streq $(systemctl show -P SoftRebootsCount) 1
+ assert_soft_reboot_count 1
expected_commit=$(ostree rev-parse soft-reboot-test)
test -f /etc/new-file-for-soft-reboot
test -f /usr/share/test-file-for-soft-reboot
- # Verify that soft-reboot-pending file is cleaned up
+ # Verify that soft-reboot state files are gone
test '!' -f /run/ostree/nextroot-booted
echo "Soft reboot test completed successfully!"
+
+ # Now soft reboot again into the rollback which is not staged,
+ # and also exercise the immediate --reboot flag.
+ touch /etc/current-contents
+ /tmp/autopkgtest-soft-reboot-prepare "3"
+ ostree admin prepare-soft-reboot --reboot 1
+ ;;
+ "3")
+ assert_soft_reboot_count 2
+
+ # Only from the first updated target
+ test '!' -f /etc/new-file-for-soft-reboot
+ test '!' -f /usr/share/test-file-for-soft-reboot
+ # And this was in the *previous* current /etc
+ test '!' -f /etc/current-contents
+
+ echo "ok verified prepare"
+
+ assert_status_jq '.deployments[0].["soft-reboot-target"] | not'
+
+ ostree admin prepare-soft-reboot 0
+ assert_status_jq '.deployments[0].["soft-reboot-target"]'
+ ostree admin prepare-soft-reboot --reset
+ assert_status_jq '.deployments[0].["soft-reboot-target"] | not'
+ test '!' -f /run/ostree/nextroot-booted
+ # Test idempotence
+ ostree admin prepare-soft-reboot --reset
+ assert_status_jq '.deployments[0].["soft-reboot-target"] | not'
+ test '!' -f /run/ostree/nextroot-booted
+
+ echo "ok soft reboot 3"
+
+ # Now, test the intersection of staged deployments and soft rebooting
+ # Create another synthetic commit
+ cd /ostree/repo/tmp
+ ostree checkout -H ${host_commit} t
+ unshare -m /bin/sh -c 'mount -o remount,rw /sysroot && cd /ostree/repo/tmp/t && touch usr/share/test-staged-2-for-soft-reboot'
+ ostree commit --no-bindings --parent="${host_commit}" -b soft-reboot-test-staged-2 -I --consume t
+ newcommit=$(ostree rev-parse soft-reboot-test-staged-2)
+ ostree admin deploy --stage soft-reboot-test-staged-2
+
+ assert_status_jq '.deployments[0].staged' '.deployments[0].["soft-reboot-target"] | not' \
+ '.deployments[1].booted | not' '.deployments[1].["soft-reboot-target"] | not' \
+ '.deployments[2].booted' '.deployments[2].["soft-reboot-target"] | not'
+
+ # Note here we're targeting the previous booted deployment, *not* the staged
+ ostree admin prepare-soft-reboot 1
+
+ # We set up the soft reboot, but cleared the staged
+ assert_status_jq '.deployments[0].booted | not' '.deployments[0].["soft-reboot-target"]' \
+ '.deployments[1].booted' '.deployments[1].["soft-reboot-target"] | not'
+
+ # And this one verifies we do a soft reboot by default as we've mounted /run/nextroot
+ /tmp/autopkgtest-soft-reboot-prepare "4"
+ systemctl reboot
+ ;;
+ "4")
+ assert_soft_reboot_count 3
+ # Completion of soft reboot into non-staged
+ assert_status_jq '.deployments[0].booted' '.deployments[0].["soft-reboot-target"] | not' \
+ '.deployments[1].booted | not' '.deployments[1].["soft-reboot-target"] | not'
+ echo "ok soft reboot to non-staged"
+
+ echo "ok soft reboot all tests"
;;
*)
fatal "Unexpected AUTOPKGTEST_REBOOT_MARK=${AUTOPKGTEST_REBOOT_MARK}"